
#include <proto_head.h>
#include <lsi.h>
#include <hash.h>
#include <xml_wrapper.h>
#include <sys/time.h>
#include <mm.h>
#include <crc32.h>
#include <util.h>
#include <event2/util.h>
#include <encrypt.h>

#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/tcp.h>

#include "proto.h"

typedef struct ClientCtx
{
	struct sockaddr server_addr;
	int fd;
	
	char send_buffer[64 * 1024];
	int nsend;

	char recv_buffer[64 * 1024];
	int nrecv;
	struct conn_head_t head;
	struct C2S_PKG pkg;
	int32_t encrypt_key;
}ClientCtx;

void usage()
{
	printf("usage: ./client server_ip server_addr\n");
}

int parse(char* cmd, struct C2S_PKG* req)
{
	assert(cmd && req);
	size_t len = strlen(cmd);
	assert(len > 0);

	int i;
	for(i=0; i<len; i++)
	{
		if(cmd[i] == '+')
		{
			req->calc_op = E_CALC_ADD;
			req->arg_1 = atoi(cmd);
			req->arg_2 = atoi(cmd + i + 1);
			return 0;
		}
		if(cmd[i] == '-')
		{
			req->calc_op = E_CALC_SUB;
			req->arg_1 = atoi(cmd);
			req->arg_2 = atoi(cmd + i + 1);
			return 0;
		}
		if(cmd[i] == '*')
		{
			req->calc_op = E_CALC_MUL;
			req->arg_1 = atoi(cmd);
			req->arg_2 = atoi(cmd + i + 1);
			return 0;
		}
		if(cmd[i] == '/')
		{
			req->calc_op = E_CALC_DIV;
			req->arg_1 = atoi(cmd);
			req->arg_2 = atoi(cmd + i + 1);
			return 0;
		}
	}
	return -1;
}

int conn_send(struct ClientCtx* ctx, int len)
{
	assert(ctx && ctx->fd >= 0 && len > 0 && len < sizeof(ctx->send_buffer));

	fd_set write_events;
	struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;
	int write_buffer_len = 0;
	int res = 0;
	
	while(write_buffer_len < len)
	{
		/* no fd to use */
		if(res < 0)
		{
			FD_ZERO(&write_events);
			FD_SET(ctx->fd, &write_events);
			int ret = select(ctx->fd + 1, NULL, &write_events, &write_events, &tv);
			if(0 == ret)
				return write_buffer_len > 0 ? write_buffer_len : -1;
		}

		/* do write */
		res = send(ctx->fd, ctx->send_buffer + write_buffer_len, len - write_buffer_len, 0);
		if(res <= 0 && (EINTR == errno || EAGAIN == errno || EWOULDBLOCK == errno))
			continue;
		else if(res <= 0)
			return write_buffer_len > 0 ? write_buffer_len : -errno;
		write_buffer_len += res;
	}
	return write_buffer_len;
}

void conn_disconn(struct ClientCtx* ctx)
{
	assert(ctx && ctx->fd >= 0);

	close(ctx->fd);
	ctx->fd = -1;
}

void conn_recv(struct ClientCtx* ctx)
{
	assert(ctx && ctx->fd >= 0);

	while(1)
	{
		char* recvbuf = ctx->recv_buffer + ctx->nrecv;
		int recvlen = sizeof(ctx->recv_buffer) - ctx->nrecv;
		int nrecv = recv(ctx->fd, recvbuf, recvlen, 0);
		if(nrecv > 0)
			ctx->nrecv += nrecv;

		if(ctx->nrecv >= sizeof(struct conn_head_t))
		{
			int ret = deserialize_conn_head(&ctx->head, ctx->recv_buffer, sizeof(struct conn_head_t));
			assert(0 == ret);
			printf("get pkg cmd=%d\n", ctx->head.cmd);
			
			if(ctx->head.cmd == ID_CONN2C_NTF_PKG)
			{
				if(ctx->nrecv >= sizeof(struct conn_head_t) + ctx->head.pkg_len)
				{
					// check crc
					char* pkgbuf = ctx->recv_buffer + sizeof(struct conn_head_t);
					int crc = crc32(pkgbuf, ctx->head.pkg_len);
					assert(crc == ctx->head.pkg_crc);
					
					/* descrypt */
					static char descrpt[64 * 1024];
					size_t deslen = sizeof(descrpt);
					ret = base_descrypt(pkgbuf, ctx->head.pkg_len, ctx->encrypt_key, descrpt, &deslen);
					assert(0 == ret);
					ret = deserialize_pkg(&ctx->pkg, descrpt, deslen);
					assert(0 == ret);

					/* pop recv message */
					memcpy(ctx->recv_buffer, 
						ctx->recv_buffer + sizeof(struct conn_head_t) + ctx->head.pkg_len, 
						ctx->nrecv - sizeof(struct conn_head_t) - ctx->head.pkg_len);
					ctx->nrecv -= (sizeof(struct conn_head_t) + ctx->head.pkg_len);
					break;
				}
			}
			else
			{
				memcpy(ctx->recv_buffer, 
					ctx->recv_buffer + sizeof(struct conn_head_t), 
					ctx->nrecv - sizeof(struct conn_head_t));
				ctx->nrecv -= sizeof(struct conn_head_t);
				break;
			}

			usleep(10);
		}
	}
}

void do_client_syn(struct ClientCtx* ctx)
{
	struct conn_head_t head;
	memset(&head, 0, sizeof(head));
	head.cmd = ID_C2CONN_SYN;
	head.flag = 1;
	size_t buflen = sizeof(ctx->send_buffer);
	int ret = serialize_conn_head(&head, ctx->send_buffer, &buflen);
	assert(0 == ret);

	ret = conn_send(ctx, buflen);
	assert(ret == buflen);
}

void do_client_ack(struct ClientCtx* ctx)
{
	struct conn_head_t head;
	memset(&head, 0, sizeof(head));
	head.cmd = ID_C2CONN_ACK;
	head.flag = ctx->encrypt_key;
	size_t buflen = sizeof(ctx->send_buffer);
	int ret = serialize_conn_head(&head, ctx->send_buffer, &buflen);
	assert(0 == ret);

	ret = conn_send(ctx, buflen);
	assert(ret == buflen);
}

void do_client_send(struct ClientCtx* ctx, struct C2S_PKG* pkg)
{
	assert(ctx && pkg);
	struct conn_head_t head;
	int ret;
	
	/* conn head */
	memset(&head, 0, sizeof(head));
	head.cmd = ID_C2CONN_NTF_PKG;
	head.flag = 0;

	/* serialize */
	char sera[1024];
	size_t sera_len = sizeof(sera);
	ret = serialize_pkg(pkg, sera, &sera_len);
	assert(0 == ret);

	/* encrypt */
	char* encry = ctx->send_buffer + sizeof(head);
	size_t encry_len = sizeof(ctx->send_buffer) - sizeof(head);
	ret = base_encrypt(sera, sera_len, ctx->encrypt_key, encry, &encry_len);
	assert(0 == ret);

	/* crc and len */
	head.pkg_crc = crc32(encry, encry_len);
	head.pkg_len = encry_len;

	/* serial head */
	char* headbuf = ctx->send_buffer;
	size_t headbuf_len = sizeof(head);
	ret = serialize_conn_head(&head, headbuf, &headbuf_len);
	assert(0 == ret);

	/* send */
	ret = conn_send(ctx, headbuf_len + encry_len);
	assert(ret == headbuf_len + encry_len);
}

void build_connection(struct ClientCtx* ctx)
{
	ctx->fd = socket(AF_INET, SOCK_STREAM, 0);
	assert(ctx->fd >= 0);

	int flags = fcntl(ctx->fd, F_GETFL, 0);
	flags |= O_NONBLOCK;
	fcntl(ctx->fd, F_SETFL, flags);

	int ret = connect(ctx->fd, &ctx->server_addr, sizeof(struct sockaddr));
	if(ret < 0)
	{
		assert(EINPROGRESS == errno);
		fd_set read_events, write_events, exec_events;
		FD_ZERO(&read_events);
		FD_SET(ctx->fd, &read_events);
		write_events = read_events;
		exec_events = read_events;
		
		struct timeval tv;
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		ret = select(ctx->fd + 1, &read_events, &write_events, &exec_events, &tv);
		assert(ret > 0);

		if(!FD_ISSET(ctx->fd, &read_events) && !FD_ISSET(ctx->fd, &write_events))
			assert(0);
		int err;
		socklen_t len = sizeof(err);
		getsockopt(ctx->fd, SOL_SOCKET, SO_ERROR, &err, &len);
		assert(0 == err);
	}

	int snd_buf_size = 256 * 1024;
	setsockopt(ctx->fd, SOL_SOCKET, SO_SNDBUF, (char*)&snd_buf_size, sizeof(snd_buf_size));
	int rcv_buf_size = 256 * 1024;
	setsockopt(ctx->fd, SOL_SOCKET, SO_RCVBUF, (char*)&rcv_buf_size, sizeof(rcv_buf_size));
	int no_delay = 1;
	setsockopt(ctx->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&no_delay, sizeof(no_delay));
}

void build_handshake(struct ClientCtx* ctx)
{
	assert(ctx && ctx->fd >= 0);

	do_client_syn(ctx);

	conn_recv(ctx);

	if(ctx->head.cmd == ID_CONN2C_NTF_DISCONN)
	{
		printf("server disconnect reason=%d\n", ctx->head.flag);
		exit(-1);
	}
	
	assert(ctx->head.cmd == ID_CONN2C_ACK);
	ctx->encrypt_key = ctx->head.flag;

	do_client_ack(ctx);

WAIT_QUEUE:
	conn_recv(ctx);
	if(ctx->head.cmd == ID_CONN2C_NTF_DISCONN)
	{
		printf("server disconnect reason=%d\n", ctx->head.flag);
		exit(-1);
	}
	else if(ctx->head.cmd == ID_CONN2C_NTF_IN_QUEUE)
	{
		printf("in queue, index=%d\n", ctx->head.flag);
		sleep(3);
		goto WAIT_QUEUE;
	}
	else if(ctx->head.cmd == ID_CONN2C_NTF_CONNECT)
	{
		printf("build connection success\n");
	}
	else
	{
		assert(0);
	}
}


int main(int argc, char** argv)
{
	/*
	struct C2S_PKG test;
	memset(&test, 0, sizeof(test));
	test.arg_1 = 1;
	test.arg_2 = 2;
	char testbuf[1024];
	size_t testlen = sizeof(testbuf);
	assert(0 == serialize_pkg(&test, testbuf, &testlen));
	struct C2S_PKG test2;
	assert(0 == deserialize_pkg(&test2, testbuf, testlen));
	*/
	
	if(argc != 3)
	{
		usage();
		return -1;
	}

	ClientCtx ctx;
	memset(&ctx, 0, sizeof(ctx));
	
	/* server address */
	char addrstr[64];
	snprintf(addrstr, sizeof(addrstr), "%s:%s", argv[1], argv[2]);
	int server_addr_len = sizeof(struct sockaddr);
	if(evutil_parse_sockaddr_port(addrstr, &ctx.server_addr, &server_addr_len) < 0)
	{
		fprintf(stderr, "parse listen addr[%s] fail\n", addrstr);
		return -1;
	}
	
	/* connect and handshake */
	build_connection(&ctx);
	build_handshake(&ctx);

CALC:	
	/* get request */
	printf("\nplease input like 111+222, supported calculate: + - * /\n");
	char cmd[256];
	memset(cmd, 0, sizeof(cmd));
	fgets(cmd, sizeof(cmd) - 1, stdin);

	struct C2S_PKG pkg;
	memset(&pkg, 0, sizeof(pkg));
	size_t i;
	for(i=0; i<sizeof(pkg.buffer); i++)
		pkg.buffer[i] = (i % 26 + 'a');
	pkg.cmd = ID_C2S_REQ_CALC;
	if(parse(cmd, &pkg) < 0)
	{
		conn_disconn(&ctx);
		return -1;
	}

	/* send pkg */
	do_client_send(&ctx, &pkg);
	
	/* wait pkg */
	conn_recv(&ctx);
	if(ctx.head.cmd == ID_CONN2C_NTF_PKG)
	{
		assert(ctx.pkg.cmd == ID_S2C_RES_CALC);
		printf("get result: %d\n", ctx.pkg.arg_3);
		goto CALC;
	}
	else if(ctx.head.cmd == ID_CONN2C_NTF_DISCONN)
	{
		printf("server disconnect reason=%d\n", ctx.head.flag);
		return 0;
	}
	else
		assert(0);
}

